package base

import (
	"errors"
	"gitee.com/lsy007/mysqlclient"
	"gitee.com/lsy007/mysqlclient/param"
	"github.com/golang/protobuf/proto"
	"go.uber.org/zap"
)

type MysqlHandle struct {
	Client    *mysqlclient.Client
	RequestId string
	Table     string
	Model     proto.Message
	Region    string
	SqlScenes string
	Logger    *zap.Logger
}

type GetInfo struct {
	AutoCondition int64
	Alias         string
	Order         string
	Field         []string
	Cols          []string
	Omit          []string
	Where         []*Where
	Group         string
	Having        string
	Join          []*JoinInfo
	Result        interface{}   // 接收结果数据内存地址，可以为 map[string]string 或者 字段值全为 string 类型的结构体
	RelatedResult proto.Message // 接收protobuf关联模型表结果数据内存地址
}

type FindInfo struct {
	Alias      string
	Field      []string
	Cols       []string
	Page       int64
	Rows       int64
	Order      string
	Where      []*Where
	Group      string
	Having     string
	Join       []*JoinInfo
	Result     interface{}   // 接收结果数据内存地址，可以为 map[string]string 或者 字段值全为 string 类型的结构体
	ListResult proto.Message // 接收列表结果数据内存地址
}

type UpdateInfo struct {
	Id    int64
	Cols  []string
	Omit  []string
	Where []*Where
}

type Where struct {
	Column   string
	Operator string
	Value    interface{}
}

func (m *MysqlHandle) Exist(autoCondition int64, where ...*Where) (bool, error) {
	reply, err := m.SendRequest(&mysqlclient.DBInfo{
		Table: m.Table, Func: "Exist", Model: m.Model, AutoCondition: autoCondition, Where: getWhere(where)})
	return reply.Has, err
}

func (m *MysqlHandle) Count(info ...*GetInfo) (int64, error) {
	dbInfo := mysqlclient.DBInfo{Table: m.Table, Func: "Count", Model: m.Model}
	if len(info) != 0 {
		dbInfo.AutoCondition = info[0].AutoCondition
		dbInfo.Where = getWhere(info[0].Where)
		dbInfo.Group = info[0].Group
		dbInfo.Having = info[0].Having
	}

	reply, err := m.SendRequest(&dbInfo)
	if err != nil {
		return 0, err
	}

	return reply.Value.Int, err
}

func (m *MysqlHandle) Get(info ...*GetInfo) (bool, error) {
	dbInfo := mysqlclient.DBInfo{Table: m.Table, Func: "Get", Model: m.Model}
	if len(info) != 0 {
		dbInfo.AutoCondition = info[0].AutoCondition
		dbInfo.Alias = info[0].Alias
		dbInfo.Order = info[0].Order
		dbInfo.Field = info[0].Field
		dbInfo.Cols = info[0].Cols
		dbInfo.Omit = info[0].Omit
		dbInfo.Where = getWhere(info[0].Where)
		dbInfo.Group = info[0].Group
		dbInfo.Having = info[0].Having
	}
	reply, err := m.SendRequest(&dbInfo)
	if err != nil {
		return false, err
	}
	if !reply.Has {
		return false, err
	}
	if err = proto.Unmarshal(reply.Model, m.Model); err != nil {
		return false, err
	}
	return reply.Has, err
}

func (m *MysqlHandle) Find(info *FindInfo) (bool, error) {
	// 1. 执行请求
	reply, err := m.SendRequest(&mysqlclient.DBInfo{
		Table: m.Table, Func: "Find", Model: m.Model, Field: info.Field, Cols: info.Cols, Alias: info.Alias,
		Start: (info.Page - 1) * info.Rows, Rows: info.Rows, Order: info.Order, Where: getWhere(info.Where),
		Group: info.Group, Having: info.Having,
	})
	if err != nil {
		return false, err
	}
	if !reply.Has {
		return false, err
	}
	// 2. 解析值
	err = proto.Unmarshal(reply.Model, info.ListResult)
	return reply.Has, err
}

func (m *MysqlHandle) FindAndCount(info *FindInfo) (int64, error) {
	// 1. 执行请求
	reply, err := m.SendRequest(&mysqlclient.DBInfo{
		Table: m.Table, Func: "FindAndCount", Model: m.Model, Field: info.Field, Cols: info.Cols, Alias: info.Alias,
		Start: (info.Page - 1) * info.Rows, Rows: info.Rows, Order: info.Order, Where: getWhere(info.Where),
		Group: info.Group, Having: info.Having,
	})
	if err != nil {
		return 0, err
	}
	if !reply.Has {
		return 0, err
	}
	if reply.Value == nil {
		m.Logger.Error("sql result value is nil", zap.Any("reply", reply))
		return 0, errors.New("sql result value is nil")
	}
	// 2. 解析值
	err = proto.Unmarshal(reply.Model, info.ListResult)
	return reply.Value.Int, err
}

func (m *MysqlHandle) Update(info *UpdateInfo) (affect int64, err error) {
	reply, err := m.SendRequest(&mysqlclient.DBInfo{
		Table: m.Table, Func: "Update", Model: m.Model, Id: info.Id, Cols: info.Cols, Omit: info.Omit, Where: getWhere(info.Where)})
	if err != nil {
		return 0, err
	}
	return reply.Value.Int, err
}

func (m *MysqlHandle) Delete(autoCondition int64, where ...*Where) (affect int64, err error) {
	reply, err := m.SendRequest(&mysqlclient.DBInfo{
		Table: m.Table, Func: "Delete", Model: m.Model, AutoCondition: autoCondition, Where: getWhere(where)})
	if err != nil {
		return 0, err
	}
	return reply.Value.Int, err
}

func (m *MysqlHandle) InsertOne() (affect int64, err error) {
	reply, err := m.SendRequest(&mysqlclient.DBInfo{Table: m.Table, Func: "InsertOne", Model: m.Model})
	if err != nil {
		return 0, err
	}
	return reply.Value.Int, err
}

func (m *MysqlHandle) InsertList() (affect int64, err error) {
	reply, err := m.SendRequest(&mysqlclient.DBInfo{Table: m.Table, Func: "InsertList", Model: m.Model, Type: 1})
	if err != nil {
		return 0, err
	}
	return reply.Value.Int, err
}

func getWhere(where []*Where) []*mysqlclient.Condition {
	if where == nil {
		return nil
	}
	cond := make([]*mysqlclient.Condition, 0)
	for _, v := range where {
		cond = append(cond, &mysqlclient.Condition{Column: v.Column, Operator: v.Operator, Value: v.Value})
	}
	return cond
}

func (m *MysqlHandle) SendRequest(info *mysqlclient.DBInfo) (reply param.Response, err error) {
	client := m.Client
	client.RequestId = m.RequestId
	client.Region = m.Region
	client.SqlScenes = m.SqlScenes
	client.DBInfo = info
	reply, err = client.SingleRequest()
	if m.Logger != nil {
		m.Logger.Debug("mysqlClient request data",
			zap.Any("db_info", []interface{}{info.Model, info.Func, info.Table}),
			zap.Any("header", client.Header),
			zap.Any("reply", []interface{}{reply.RequestId, reply.Region, reply.Has, reply.Value, reply.Type, reply.ErrMsg}),
		)
	}
	return
}
